package com.google.code.gronono.commons.swing.dialog;

import java.awt.Font;
import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;

import javax.swing.GroupLayout;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JProgressBar;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.GroupLayout.Alignment;
import javax.swing.LayoutStyle.ComponentPlacement;

import com.google.code.gronono.commons.i18n.BundleKey;
import com.google.code.gronono.commons.i18n.BundleName;
import com.google.code.gronono.commons.swing.SwingUtils;

/**
 * Boite de dialogue affichant une barre de progression infinie.
 *
 * @author Arnaud BRUNET
 */
@BundleName(value = "com.google.code.gronono.commons.swing")
public class ProgressDialog extends JDialog {
	/** serialVersionUID. */
	private static final long serialVersionUID = 7960991330759890830L;

	/** Largeur de la barre. */
	@BundleKey(value = "progress.bar.width")
	private static int PROGESS_BAR_WIDTH;
	/** Titre de la boite de dialogue. */
	@BundleKey(value = "progress.dialog.title")
	private static String DIALOG_TITLE;
	/** Description de la tache en cours. */
	@BundleKey(value = "progress.dialog.description")
	private static String DESCRIPTION_LBL;
	/** Label du bouton Annuler. */
	@BundleKey(value = "progress.dialog.btn.cancel")
	private static String CANCEL_BTN;
	/** Étape en cours. */
	@BundleKey(value = "progress.dialog.step")
	private static String STEP_LBL;
	/** Nombre de caractères maximum dans la description et l'étape. */
	@BundleKey(value = "progress.dialog.max.char")
	private static int MAX_CHAR;

	/** Label contenant la description. */
	private JLabel titleLbl;
	/** La barre de progression. */
	private JProgressBar progressBar;
	/** Label contenant l'étape. */
	private JLabel stepLbl;
	/** Le bouton annuler. */
	private JButton cancelBtn;

	/** Le process en cours d'exécution */
	private SwingWorker<Void, Void> worker;

	/**
	 * Construit la boite de dialogue.
	 * @param parent La fenêtre parente.
	 */
	public ProgressDialog(final Frame parent) {
		this(parent, DIALOG_TITLE);
	}

	/**
	 * Construit la boite de dialogue.
	 * @param parent La fenêtre parente.
	 * @param title Le titre de la boite de dialogue.
	 */
	public ProgressDialog(final Frame parent, final String title) {
		super(parent, title, true);

		// Création du contenu
		initComponents();
		placeComponents();

		// A la fermeture de la fenêtre, on annule le process en cours
		this.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(final WindowEvent e) {
				if (worker != null) {
					worker.cancel(true);
				}
			}
		});
	}

	/**
	 * Change la description de l'opération en cours.
	 * @param text La nouvelle description.
	 */
	public void setDescription(final String text) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				titleLbl.setText(text.substring(0, Math.min(text.length(), MAX_CHAR)));
			}
		});
	}

	/**
	 * Change l'étape en cours.
	 * @param step La nouvelle étape.
	 */
	public void setStep(final String step) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				stepLbl.setText(step.substring(0, Math.min(step.length(), MAX_CHAR)));
			}
		});
	}

	/**
     * Initialise les différents composants.
     */
	private void initComponents() {
		titleLbl = new JLabel(DESCRIPTION_LBL);
		titleLbl.setFont(titleLbl.getFont().deriveFont(Font.BOLD));
		progressBar = new JProgressBar();
		progressBar.setIndeterminate(true);
		stepLbl = new JLabel(STEP_LBL);
		cancelBtn = new JButton(CANCEL_BTN);
		cancelBtn.addActionListener(new ActionListener() {
			public void actionPerformed(final ActionEvent e) {
				worker.cancel(true);
				dispose();
			}
		});
	}

	/**
     * Place les composants dans la fenêtre.
     */
	private void placeComponents() {
		final GroupLayout layout = new GroupLayout(getContentPane());
        getContentPane().setLayout(layout);

        // Horizontalement
        layout.setHorizontalGroup(
            layout.createParallelGroup()
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup()
                    .addComponent(progressBar, GroupLayout.DEFAULT_SIZE, PROGESS_BAR_WIDTH, Short.MAX_VALUE)
                    .addComponent(cancelBtn, Alignment.TRAILING)
                    .addComponent(titleLbl, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addGap(10, 10, 10)
                        .addComponent(stepLbl, GroupLayout.DEFAULT_SIZE, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)))
                .addContainerGap())
        );
        // Verticalement
        layout.setVerticalGroup(
            layout.createParallelGroup()
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(titleLbl)
                .addPreferredGap(ComponentPlacement.RELATED)
                .addComponent(progressBar, GroupLayout.PREFERRED_SIZE, GroupLayout.DEFAULT_SIZE, GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(ComponentPlacement.RELATED)
                .addComponent(stepLbl)
                .addPreferredGap(ComponentPlacement.RELATED, GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                .addComponent(cancelBtn)
                .addContainerGap())
        );

        SwingUtils.pack(this);
	}

	/**
	 * Lance le {@link Runnable} spécifié et ouvre la boite de dialogue.
	 * @param runnable Le process à lancer.
	 */
	public void start(final Runnable runnable) {
		// Lancement du worker
		this.worker = new SwingWorker<Void, Void>() {
			@Override
			protected Void doInBackground() throws Exception {
				runnable.run();
				return null;
			}

			@Override
			protected void done() {
				dispose();
			}
		};
		worker.execute();

		// Déplacement de la fenêtre au centre de son parent
		this.setLocationRelativeTo(this.getParent());

		// Affichage de la fenêtre
		this.setVisible(true);
	}

	/**
	 * Permet de savoir si le process a été annulé.
	 * @return <code>true</code> si process a été annulé, <code>false</code> sinon.
	 */
	public boolean isCancelled() {
		return worker.isCancelled();
	}

	/**
	 * Permet d'interrompre le process en cours.
	 */
	public void setCancelled() {
		worker.cancel(true);
	}

}
